package mycobra

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"

	flag "github.com/spf13/pflag"
)

// Command is  a command for the application.
// User needs to define the usage and description as part of the command definition to ensure usability.
type Command struct {
	// Use is the one-line usage message.
	Use string

	// Short is the short description shown in the 'help' output.
	Short string

	// Long is the long message shown in the 'help <this-command>' output.
	Long string

	// Run: Typically the actual work function. Most commands will only implement this.
	Run func(cmd *Command, args []string)

	// commands is the list of commands supported by this program.
	commands []*Command

	// parent is a parent command for this command.
	parent *Command

	// args is actual args parsed from flags.
	args []string

	// flags is full set of flags.
	flags *flag.FlagSet
}

// Execute uses the args and run through the command tree finding appropriate matches for commands and then corresponding flags.
func (c *Command) Execute() error {
	args := c.args
	if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
		args = os.Args[1:]
	}

	cmd, flags, err := c.Find(args)

	if err != nil {
		return err
	}

	for _, f := range flags {
		if f == "-h" || f == "--help" {
			cmd.HelpFunc()
			return nil
		}
	}

	err = cmd.execute(flags)
	if err != nil {
		if err == flag.ErrHelp {
			cmd.HelpFunc()
			return nil
		}
	}

	return err
}

//execute executes the command.
func (c *Command) execute(a []string) (err error) {
	if c == nil {
		return fmt.Errorf("Called Execute() on a nil Command")
	}

	err = c.ParseFlags(a)

	if c.Run == nil {
		return flag.ErrHelp
	}

	c.Run(c, a)

	return nil
}

// ParseFlags parses persistent flag tree and local flags.
func (c *Command) ParseFlags(args []string) error {
	err := c.Flags().Parse(args)

	return err
}

// Flags returns the complete FlagSet that applies
// to this command (local and persistent declared here and by all parents).
func (c *Command) Flags() *flag.FlagSet {
	if c.flags == nil {
		c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
	}

	return c.flags
}

// Find the target command given the args and command tree
// Meant to be run on the highest node. Only searches down.
func (c *Command) Find(args []string) (*Command, []string, error) {
	var innerfind func(*Command, []string) (*Command, []string)

	innerfind = func(c *Command, innerArgs []string) (*Command, []string) {
		argsWOflags := innerArgs
		if len(argsWOflags) == 0 {
			return c, innerArgs
		}
		nextSubCmd := argsWOflags[0]

		cmd := c.findNext(nextSubCmd)
		if cmd != nil {
			return innerfind(cmd, argsMinusFirstX(innerArgs, nextSubCmd))
		}
		return c, innerArgs
	}

	commandFound, a := innerfind(c, args)

	return commandFound, a, nil
}

func (c *Command) findNext(next string) *Command {
	for _, cmd := range c.commands {
		if cmd.Name() == next {
			return cmd
		}
	}
	return nil
}

// argsMinusFirstX removes only the first x from args.
func argsMinusFirstX(args []string, x string) []string {
	for i, y := range args {
		if x == y {
			ret := []string{}
			ret = append(ret, args[:i]...)
			ret = append(ret, args[i+1:]...)
			return ret
		}
	}
	return args
}

// AddCommand adds one or more commands to this parent command.
func (c *Command) AddCommand(cmds ...*Command) {
	for i, x := range cmds {
		if cmds[i] == c {
			panic("Command can't be a child of itself")
		}
		cmds[i].parent = c
		c.commands = append(c.commands, x)
	}
}

// RemoveCommand removes one or more commands from a parent command.
func (c *Command) RemoveCommand(cmds ...*Command) {
	commands := []*Command{}
main:
	for _, command := range c.commands {
		for _, cmd := range cmds {
			if command == cmd {
				command.parent = nil
				continue main
			}
		}
		commands = append(commands, command)
	}
	c.commands = commands
}

// Name returns the command's name: the first word in the use line.
func (c *Command) Name() string {
	name := c.Use
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

// HelpFunc returns either the function set by SetHelpFunc for this command
// or a parent, or it returns a function with default help behavior.
func (c *Command) HelpFunc() {
	fmt.Printf("%s\n\n", c.Long)

	fmt.Printf("Usage:\n")
	fmt.Printf("  %s [flags]\n", c.Name())
	if len(c.commands) > 0 {
		fmt.Printf("  %s [command]\n\n", c.Name())
		fmt.Printf("Available Commands:\n")
		for _, v := range c.commands {
			fmt.Printf("  %-10s%s\n", v.Name(), v.Short)
		}
	}
	fmt.Printf("\nFlags:\n")

	c.Flags().VisitAll(func(flag *flag.Flag) {
		fmt.Printf("  -%1s, --%-6s %-12s%s (default \"%s\")\n", flag.Shorthand, flag.Name, flag.Value.Type(), flag.Usage, flag.DefValue)
	})

	fmt.Printf("  -%1s, --%-10s%s%s\n", "h", "help", "help for ", c.Name())
	fmt.Println()
	if len(c.commands) > 0 {
		fmt.Printf("Use \"%s [command] --help\" for more information about a command.\n", c.Name())
	}
}
